/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.x.component.connectivity;

import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN;
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT;
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE;
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
import android.telephony.CellSignalStrength;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.MathUtils;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.TelephonyIntents;
import com.x.component.connectivity.MobileMappings.Config;
import com.x.component.connectivity.MobileStatusTracker.SubscriptionDefaults;

import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Executor;


/** Platform implementation of the network controller. **/
public class NetworkControllerImpl extends BroadcastReceiver
        implements NetworkController, DataUsageController.NetworkNameProvider {
    // debug
    static final String TAG = "NetworkController";
    static final boolean DEBUG = true; //Log.isLoggable(TAG, Log.DEBUG);
    // additional diagnostics, but not logspew
    static final boolean CHATTY =  true; // Log.isLoggable(TAG + "Chat", Log.DEBUG);

    private static final int EMERGENCY_NO_CONTROLLERS = 0;
    private static final int EMERGENCY_FIRST_CONTROLLER = 100;
    private static final int EMERGENCY_VOICE_CONTROLLER = 200;
    private static final int EMERGENCY_NO_SUB = 300;
    private static final int EMERGENCY_ASSUMED_VOICE_CONTROLLER = 400;
    private static final int HISTORY_SIZE = 16;
    private static final SimpleDateFormat SSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");

    private final Context mContext;
    private final TelephonyManager mPhone;
    private final WifiManager mWifiManager;
    private final ConnectivityManager mConnectivityManager;
    private final SubscriptionManager mSubscriptionManager;
    private final boolean mHasMobileDataFeature;
    private final SubscriptionDefaults mSubDefaults;
    private final Object mLock = new Object();
    private final boolean mProviderModelBehavior;
    private Config mConfig;

    private TelephonyCallback.ActiveDataSubscriptionIdListener mPhoneStateListener;
    private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;

    // Subcontrollers.
    
    final WifiSignalController mWifiSignalController;


    
    final SparseArray<MobileSignalController> mMobileSignalControllers = new SparseArray<>();
    // When no SIMs are around at setup, and one is added later, it seems to default to the first
    // SIM for most actions.  This may be null if there aren't any SIMs around.
    private MobileSignalController mDefaultSignalController;
    private final DataUsageController mDataUsageController;

    private boolean mInetCondition; // Used for Logging and demo.

    // BitSets indicating which network transport types (e.g., TRANSPORT_WIFI, TRANSPORT_MOBILE) are
    // connected and validated, respectively.
    private final BitSet mConnectedTransports = new BitSet();
    private final BitSet mValidatedTransports = new BitSet();

    // States that don't belong to a subcontroller.
    private boolean mAirplaneMode = false;
    private boolean mHasNoSubs;
    private boolean mNoDefaultNetwork = false;
    private boolean mNoNetworksAvailable = true;
    private Locale mLocale = null;
    // This list holds our ordering.
    private List<SubscriptionInfo> mCurrentSubscriptions = new ArrayList<>();

    // Save the previous HISTORY_SIZE states for logging.
    private final String[] mHistory = new String[HISTORY_SIZE];
    // Where to copy the next state into.
    private int mHistoryIndex;

    
    boolean mListening;

    // The current user ID.
    private int mCurrentUserId;

    private OnSubscriptionsChangedListener mSubscriptionListener;
    private NetworkCapabilities mLastDefaultNetworkCapabilities;
    // Handler that all broadcasts are received on.
    private final Handler mReceiverHandler;
    private final Looper mBgLooper;
    private final Executor mBgExecutor;
    // Handler that all callbacks are made on.
    private final CallbackHandler mCallbackHandler;

    private int mEmergencySource;
    private boolean mIsEmergency;

    
    ServiceState mLastServiceState;
    private boolean mUserSetup;
    private boolean mSimDetected;
    private boolean mForceCellularValidated;
    private Handler mMainHandler;


    NetworkControllerImpl(Context context, ConnectivityManager connectivityManager,
            TelephonyManager telephonyManager,
            WifiManager wifiManager,
            SubscriptionManager subManager,
            Config config,
            Looper bgLooper,
            Executor bgExecutor,
            CallbackHandler callbackHandler,
            DataUsageController dataUsageController,
            SubscriptionDefaults defaultsHandler,
            WifiStatusTrackerFactory trackerFactory,
            Handler handler
    ) {
        mContext = context;
        mConfig = config;
        mMainHandler = handler;
        mReceiverHandler = new Handler(bgLooper);
        mBgLooper = bgLooper;
        mBgExecutor = bgExecutor;
        mCallbackHandler = callbackHandler;

        mSubscriptionManager = subManager;
        mSubDefaults = defaultsHandler;
        mConnectivityManager = connectivityManager;
        mHasMobileDataFeature = telephonyManager.isDataCapable();

        // telephony
        mPhone = telephonyManager;

        // wifi
        mWifiManager = wifiManager;

        mLocale = mContext.getResources().getConfiguration().locale;
        mDataUsageController = dataUsageController;
        mDataUsageController.setNetworkController(this);
        // TODO: Find a way to move this into DataUsageController.
        mDataUsageController.setCallback(new DataUsageController.Callback() {
            @Override
            public void onMobileDataEnabled(boolean enabled) {
                mCallbackHandler.setMobileDataEnabled(enabled);
                notifyControllersMobileDataChanged();
            }
        });

        mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature,
                mCallbackHandler, this, mWifiManager, trackerFactory,
                mReceiverHandler);


        // AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
        updateAirplaneMode(true /* force callback */);

        WifiManager.ScanResultsCallback scanResultsCallback =
                new WifiManager.ScanResultsCallback() {
            @Override
            public void onScanResultsAvailable() {
                mNoNetworksAvailable = true;
                for (ScanResult scanResult : mWifiManager.getScanResults()) {
                    if (!scanResult.SSID.equals(mWifiSignalController.getState().ssid)) {
                        mNoNetworksAvailable = false;
                        break;
                    }
                }
                // Only update the network availability if there is no default network.
                if (mNoDefaultNetwork) {
                    mCallbackHandler.setConnectivityStatus(mNoDefaultNetwork, !mInetCondition,
                            mNoNetworksAvailable);
                }
            }
        };

        if (mWifiManager != null) {
            mWifiManager.registerScanResultsCallback(new Executor() {
				
				@Override
				public void execute(Runnable command) {
					mReceiverHandler.post(command);
					
				}
			}, scanResultsCallback);
        }


        NetworkCallback callback =
                new NetworkCallback(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO){
            private Network mLastNetwork;
            private NetworkCapabilities mLastNetworkCapabilities;

            @Override
            public void onLost(Network network) {
                mLastNetwork = null;
                mLastNetworkCapabilities = null;
                mLastDefaultNetworkCapabilities = null;
                String callback = new StringBuilder()
                        .append(SSDF.format(System.currentTimeMillis())).append(",")
                        .append("onLost: ")
                        .append("network=").append(network)
                        .toString();
                recordLastNetworkCallback(callback);
                updateConnectivity();
            }

            @Override
            public void onCapabilitiesChanged(
                    Network network, NetworkCapabilities networkCapabilities) {
                boolean lastValidated = (mLastNetworkCapabilities != null)
                        && mLastNetworkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED);
                boolean validated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED);

                // This callback is invoked a lot (i.e. when RSSI changes), so avoid updating
                // icons when connectivity state has remained the same.
                if (network.equals(mLastNetwork) && validated == lastValidated) {
                    // Should not rely on getTransportTypes() returning the same order of transport
                    // types. So sort the array before comparing.
                    int[] newTypes = getProcessedTransportTypes(networkCapabilities);
                    Arrays.sort(newTypes);

                    int[] lastTypes = (mLastNetworkCapabilities != null)
                            ? getProcessedTransportTypes(mLastNetworkCapabilities) : null;
                    if (lastTypes != null) Arrays.sort(lastTypes);

                    if (Arrays.equals(newTypes, lastTypes)) {
                        return;
                    }
                }
                mLastNetwork = network;
                mLastNetworkCapabilities = networkCapabilities;
                mLastDefaultNetworkCapabilities = networkCapabilities;
                String callback = new StringBuilder()
                        .append(SSDF.format(System.currentTimeMillis())).append(",")
                        .append("onCapabilitiesChanged: ")
                        .append("network=").append(network).append(",")
                        .append("networkCapabilities=").append(networkCapabilities)
                        .toString();
                recordLastNetworkCallback(callback);
                updateConnectivity();
            }
        };
        // Even though this callback runs on the receiver handler thread which also processes the
        // CONNECTIVITY_ACTION broadcasts, the broadcast and callback might come in at different
        // times. This is safe since updateConnectivity() builds the list of transports from
        // scratch.
        // TODO: Move off of the deprecated CONNECTIVITY_ACTION broadcast and rely on callbacks
        // exclusively for status bar icons.
        mConnectivityManager.registerDefaultNetworkCallback(callback, mReceiverHandler);
        // Run the listener on our bg looper
        mPhoneStateListener = new ActiveDataSubscriptionIdListener() {
			@Override
			public void onActiveDataSubscriptionIdChanged(final int subId) {
			    mBgExecutor.execute(new Runnable() {
					@Override
					public void run() {
					    // For data switching from A to B, we assume B is validated for up to 2 seconds if:
					    // 1) A and B are in the same subscription group e.g. CBRS data switch. And
					    // 2) A was validated before the switch.
					    // This is to provide smooth transition for UI without showing cross during data
					    // switch.
					    if (keepCellularValidationBitInSwitch(mActiveMobileDataSubscription, subId)) {
					        if (DEBUG) Log.d(TAG, ": mForceCellularValidated to true.");
					        mForceCellularValidated = true;
					        mReceiverHandler.removeCallbacks(mClearForceValidated);
					        mReceiverHandler.postDelayed(mClearForceValidated, 2000);
					    }
					    mActiveMobileDataSubscription = subId;
					    doUpdateMobileControllers();
					}
				});
			}
		};

        mProviderModelBehavior = false;

        mReceiverHandler.post(mRegisterListeners);
    }

    private final Runnable mClearForceValidated = new Runnable() {
		@Override
		public void run() {
		    if (DEBUG) Log.d(TAG, ": mClearForceValidated");
		    mForceCellularValidated = false;
		    updateConnectivity();
		}
	};

    boolean isInGroupDataSwitch(int subId1, int subId2) {
        SubscriptionInfo info1 = mSubscriptionManager.getActiveSubscriptionInfo(subId1);
        SubscriptionInfo info2 = mSubscriptionManager.getActiveSubscriptionInfo(subId2);
        return (info1 != null && info2 != null && info1.getGroupUuid() != null
            && info1.getGroupUuid().equals(info2.getGroupUuid()));
    }

    boolean keepCellularValidationBitInSwitch(int sourceSubId, int destSubId) {
        return mValidatedTransports.get(TRANSPORT_CELLULAR)
                && isInGroupDataSwitch(sourceSubId, destSubId);
    }

    
    void registerListeners() {
        for (int i = 0; i < mMobileSignalControllers.size(); i++) {
            MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
            mobileSignalController.registerListener();
        }
        if (mSubscriptionListener == null) {
            mSubscriptionListener = new SubListener(mBgLooper);
        }
        mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener);
//        mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);

        // broadcasts
        IntentFilter filter = new IntentFilter();
        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
        filter.addAction(Intent.ACTION_SIM_STATE_CHANGED);
        filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
        filter.addAction(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
        filter.addAction(Intent.ACTION_SERVICE_STATE);
        filter.addAction(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED);
        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
        filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
        filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
        filter.addAction(Settings.Panel.ACTION_INTERNET_CONNECTIVITY);
        mContext.registerReceiverAsUser(this, UserHandle.ALL, filter, null, mReceiverHandler);
        mListening = true;

        // Initial setup of connectivity. Handled as if we had received a sticky broadcast of
        // ConnectivityManager.CONNECTIVITY_ACTION.
        mReceiverHandler.post(new Runnable() {
			
			@Override
			public void run() {
				updateConnectivity();
				
			}
		});

        // Initial setup of WifiSignalController. Handled as if we had received a sticky broadcast
        // of WifiManager.WIFI_STATE_CHANGED_ACTION or WifiManager.NETWORK_STATE_CHANGED_ACTION
        mReceiverHandler.post(new Runnable() {
			
			@Override
			public void run() {
				mWifiSignalController.fetchInitialState();			
			}
		});

        // Initial setup of mLastServiceState. Only run if there is no service state yet.
        // Each MobileSignalController will also get their corresponding
        mReceiverHandler.post(new Runnable() {
			@Override
			public void run() {
			    if (mLastServiceState == null) {
			        mLastServiceState = mPhone.getServiceState();
			        if (mMobileSignalControllers.size() == 0) {
			            recalculateEmergency();
			        }
			    }
			}
		});
        updateMobileControllers();

        // Initial setup of emergency information. Handled as if we had received a sticky broadcast
        // of TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED.
        mReceiverHandler.post(new Runnable() {
			
			@Override
			public void run() {
				recalculateEmergency();				
			}
		});
    }

    private void unregisterListeners() {
        mListening = false;
        for (int i = 0; i < mMobileSignalControllers.size(); i++) {
            MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
            mobileSignalController.unregisterListener();
        }
        mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionListener);
        mContext.unregisterReceiver(this);
    }


    public int getConnectedWifiLevel() {
        return mWifiSignalController.getState().level;
    }

    @Override
    public DataUsageController getMobileDataController() {
        return mDataUsageController;
    }

    /** */
    public boolean hasMobileDataFeature() {
        return mHasMobileDataFeature;
    }

    /** */
    public boolean hasVoiceCallingFeature() {
        return mPhone.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE;
    }

    private int[] getProcessedTransportTypes(NetworkCapabilities networkCapabilities) {
        int[] transportTypes = networkCapabilities.getTransportTypes();
        for (int i = 0; i < transportTypes.length; i++) {
            // For VCN over WiFi, the transportType is set to be TRANSPORT_CELLULAR in the
            // NetworkCapabilities, but we need to convert it into TRANSPORT_WIFI in order to
            // distinguish it from VCN over Cellular.
            if (transportTypes[i] == NetworkCapabilities.TRANSPORT_CELLULAR
                    && Utils.tryGetWifiInfoForVcn(networkCapabilities) != null) {
                transportTypes[i] = NetworkCapabilities.TRANSPORT_WIFI;
                break;
            }
        }
        return transportTypes;
    }

    private MobileSignalController getDataController() {
        int dataSubId = mSubDefaults.getActiveDataSubId();
        return getControllerWithSubId(dataSubId);
    }

    private MobileSignalController getControllerWithSubId(int subId) {
        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
            if (DEBUG) Log.e(TAG, "No data sim selected");
            return mDefaultSignalController;
        }
        if (mMobileSignalControllers.indexOfKey(subId) >= 0) {
            return mMobileSignalControllers.get(subId);
        }
        if (DEBUG) Log.e(TAG, "Cannot find controller for data sub: " + subId);
        return mDefaultSignalController;
    }

    @Override
    public String getMobileDataNetworkName() {
        MobileSignalController controller = getDataController();
        return controller != null ? controller.getState().networkNameData : "";
    }

    @Override
    public boolean isMobileDataNetworkInService() {
        MobileSignalController controller = getDataController();
        return controller != null && controller.isInService();
    }

    @Override
    public int getNumberSubscriptions() {
        return mMobileSignalControllers.size();
    }

    boolean isDataControllerDisabled() {
        MobileSignalController dataController = getDataController();
        if (dataController == null) {
            return false;
        }

        return dataController.isDataDisabled();
    }

    boolean isCarrierMergedWifi(int subId) {
        return mWifiSignalController.isCarrierMergedWifi(subId);
    }

    boolean hasDefaultNetwork() {
        return !mNoDefaultNetwork;
    }

    boolean isNonCarrierWifiNetworkAvailable() {
        return !mNoNetworksAvailable;
    }

    boolean isEthernetDefault() {
        return mConnectedTransports.get(NetworkCapabilities.TRANSPORT_ETHERNET);
    }

    String getNetworkNameForCarrierWiFi(int subId) {
        MobileSignalController controller = getControllerWithSubId(subId);
        return controller != null ? controller.getNetworkNameForCarrierWiFi() : "";
    }

    void notifyWifiLevelChange(int level) {
        for (int i = 0; i < mMobileSignalControllers.size(); i++) {
            MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
            mobileSignalController.notifyWifiLevelChange(level);
        }
    }

    void notifyDefaultMobileLevelChange(int level) {
        for (int i = 0; i < mMobileSignalControllers.size(); i++) {
            MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
            mobileSignalController.notifyDefaultMobileLevelChange(level);
        }
    }

    private void notifyControllersMobileDataChanged() {
        for (int i = 0; i < mMobileSignalControllers.size(); i++) {
            MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
            mobileSignalController.onMobileDataChanged();
        }
    }

    boolean isEmergencyOnly() {
        if (mMobileSignalControllers.size() == 0) {
            // When there are no active subscriptions, determine emengency state from last
            // broadcast.
            mEmergencySource = EMERGENCY_NO_CONTROLLERS;
            return mLastServiceState != null && mLastServiceState.isEmergencyOnly();
        }
        int voiceSubId = mSubDefaults.getDefaultVoiceSubId();
        if (!SubscriptionManager.isValidSubscriptionId(voiceSubId)) {
            for (int i = 0; i < mMobileSignalControllers.size(); i++) {
                MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
                if (!mobileSignalController.getState().isEmergency) {
                    mEmergencySource = EMERGENCY_FIRST_CONTROLLER
                            + mobileSignalController.mSubscriptionInfo.getSubscriptionId();
                    if (DEBUG) Log.d(TAG, "Found emergency " + mobileSignalController.mTag);
                    return false;
                }
            }
        }
        if (mMobileSignalControllers.indexOfKey(voiceSubId) >= 0) {
            mEmergencySource = EMERGENCY_VOICE_CONTROLLER + voiceSubId;
            if (DEBUG) Log.d(TAG, "Getting emergency from " + voiceSubId);
            return mMobileSignalControllers.get(voiceSubId).getState().isEmergency;
        }
        // If we have the wrong subId but there is only one sim anyway, assume it should be the
        // default.
        if (mMobileSignalControllers.size() == 1) {
            mEmergencySource = EMERGENCY_ASSUMED_VOICE_CONTROLLER
                    + mMobileSignalControllers.keyAt(0);
            if (DEBUG)  {
                Log.d(TAG, "Getting assumed emergency from "
                        + mMobileSignalControllers.keyAt(0));
            }
            return mMobileSignalControllers.valueAt(0).getState().isEmergency;
        }
        if (DEBUG) Log.e(TAG, "Cannot find controller for voice sub: " + voiceSubId);
        mEmergencySource = EMERGENCY_NO_SUB + voiceSubId;
        // Something is wrong, better assume we can't make calls...
        return true;
    }

    /**
     * Emergency status may have changed (triggered by MobileSignalController),
     * so we should recheck and send out the state to listeners.
     */
    void recalculateEmergency() {
        mIsEmergency = isEmergencyOnly();
    }

    @Override
    public void setWifiEnabled(final boolean enabled) {
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... args) {
                mWifiManager.setWifiEnabled(enabled);
                return null;
            }
        }.execute();
    }

    private void onUserSwitched(int newUserId) {
        mCurrentUserId = newUserId;
        updateConnectivity();
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        if (CHATTY) {
            Log.d(TAG, "onReceive: intent=" + intent);
        }
        final String action = intent.getAction();
        switch (action) {
            case ConnectivityManager.CONNECTIVITY_ACTION:
                updateConnectivity();
                break;
            case Intent.ACTION_AIRPLANE_MODE_CHANGED:
                refreshLocale();
                updateAirplaneMode(false);
                break;
            case TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED:
                // We are using different subs now, we might be able to make calls.
                recalculateEmergency();
                break;
            case TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
                // Notify every MobileSignalController so they can know whether they are the
                // data sim or not.
                for (int i = 0; i < mMobileSignalControllers.size(); i++) {
                    MobileSignalController controller = mMobileSignalControllers.valueAt(i);
                    controller.handleBroadcast(intent);
                }
                mConfig = Config.readConfig(mContext);
                mReceiverHandler.post(new Runnable() {
					
					@Override
					public void run() {
						handleConfigurationChanged();						
					}
				});
                break;
            case Intent.ACTION_SIM_STATE_CHANGED:
            	Log.d(TAG, "onReceive: intent=ACTION_SIM_STATE_CHANGED");
                // Avoid rebroadcast because SysUI is direct boot aware.
                if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
                    break;
                }
                // Might have different subscriptions now.
                // 现在可能有不同的订阅。
                updateMobileControllers();
                break;
            case Intent.ACTION_SERVICE_STATE:
                mLastServiceState = ServiceState.newFromBundle(intent.getExtras());
                if (mMobileSignalControllers.size() == 0) {
                    // If none of the subscriptions are active, we might need to recalculate
                    // emergency state.
                    recalculateEmergency();
                }
                break;
            case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
                mConfig = Config.readConfig(mContext);
                mReceiverHandler.post(new Runnable() {
					
					@Override
					public void run() {
						handleConfigurationChanged();						
					}
				});
                break;
            case Settings.Panel.ACTION_INTERNET_CONNECTIVITY:
//                mMainHandler.post(() -> mInternetDialogFactory.create(true,
//                        mAccessPoints.canConfigMobileData(), mAccessPoints.canConfigWifi(),
//                        null /* view */));
                break;
            default:
                int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
                if (SubscriptionManager.isValidSubscriptionId(subId)) {
                    if (mMobileSignalControllers.indexOfKey(subId) >= 0) {
                        mMobileSignalControllers.get(subId).handleBroadcast(intent);
                    } else {
                        // Can't find this subscription...  We must be out of date.
                        updateMobileControllers();
                    }
                } else {
                    // No sub id, must be for the wifi.
                    mWifiSignalController.handleBroadcast(intent);
                }
                break;
        }
    }

    
    void handleConfigurationChanged() {
        updateMobileControllers();
        for (int i = 0; i < mMobileSignalControllers.size(); i++) {
            MobileSignalController controller = mMobileSignalControllers.valueAt(i);
            controller.setConfiguration(mConfig);
            if (mProviderModelBehavior) {
                controller.refreshCallIndicator(mCallbackHandler);
            }
        }
        refreshLocale();
    }

    private void updateMobileControllers() {
        if (!mListening) {
            return;
        }
        doUpdateMobileControllers();
    }

    private void filterMobileSubscriptionInSameGroup(List<SubscriptionInfo> subscriptions) {
        if (subscriptions.size() == 2) {
            SubscriptionInfo info1 = subscriptions.get(0);
            SubscriptionInfo info2 = subscriptions.get(1);
            if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) {
                // If both subscriptions are primary, show both.
                if (!info1.isOpportunistic() && !info2.isOpportunistic()) return;

                // If carrier required, always show signal bar of primary subscription.
                // Otherwise, show whichever subscription is currently active for Internet.
                boolean alwaysShowPrimary = CarrierConfigManager.getDefaultConfig()
                        .getBoolean(CarrierConfigManager
                        .KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN);
                if (alwaysShowPrimary) {
                    subscriptions.remove(info1.isOpportunistic() ? info1 : info2);
                } else {
                    subscriptions.remove(info1.getSubscriptionId() == mActiveMobileDataSubscription
                            ? info2 : info1);
                }
            }
        }
    }

    
    void doUpdateMobileControllers() {
        List<SubscriptionInfo> subscriptions = mSubscriptionManager
                .getCompleteActiveSubscriptionInfoList();
        if (subscriptions == null) {
            subscriptions = Collections.emptyList();
        }

        filterMobileSubscriptionInSameGroup(subscriptions);

        // If there have been no relevant changes to any of the subscriptions, we can leave as is.
        if (hasCorrectMobileControllers(subscriptions)) {
            // Even if the controllers are correct, make sure we have the right no sims state.
            // Such as on boot, don't need any controllers, because there are no sims,
            // but we still need to update the no sim state.
            updateNoSims();
            return;
        }
        synchronized (mLock) {
            setCurrentSubscriptionsLocked(subscriptions);
        }
        updateNoSims();
        recalculateEmergency();
    }

    
    protected void updateNoSims() {
        boolean hasNoSubs = mHasMobileDataFeature && mMobileSignalControllers.size() == 0;
        boolean simDetected = hasAnySim();
        if (hasNoSubs != mHasNoSubs || simDetected != mSimDetected) {
            mHasNoSubs = hasNoSubs;
            mSimDetected = simDetected;
            mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
            Log.d("NetworkController", "updateNoSims SimDetected " +mSimDetected);
            Log.d("NetworkController", "updateNoSims HasNoSubs " +mHasNoSubs);
        }
    }

    private boolean hasAnySim() {
        int simCount = mPhone.getActiveModemCount();
        for (int i = 0; i < simCount; i++) {
            int state = mPhone.getSimState(i);
            if (state != TelephonyManager.SIM_STATE_ABSENT
                    && state != TelephonyManager.SIM_STATE_UNKNOWN) {
                return true;
            }
        }
        return false;
    }

    @GuardedBy("mLock")
    void setCurrentSubscriptionsLocked(List<SubscriptionInfo> subscriptions) {
        Collections.sort(subscriptions, new Comparator<SubscriptionInfo>() {
            @Override
            public int compare(SubscriptionInfo lhs, SubscriptionInfo rhs) {
                return lhs.getSimSlotIndex() == rhs.getSimSlotIndex()
                        ? lhs.getSubscriptionId() - rhs.getSubscriptionId()
                        : lhs.getSimSlotIndex() - rhs.getSimSlotIndex();
            }
        });
        mCurrentSubscriptions = subscriptions;

        SparseArray<MobileSignalController> cachedControllers =
                new SparseArray<MobileSignalController>();
        for (int i = 0; i < mMobileSignalControllers.size(); i++) {
            cachedControllers.put(mMobileSignalControllers.keyAt(i),
                    mMobileSignalControllers.valueAt(i));
        }
        mMobileSignalControllers.clear();
        final int num = subscriptions.size();
        for (int i = 0; i < num; i++) {
            int subId = subscriptions.get(i).getSubscriptionId();
            // If we have a copy of this controller already reuse it, otherwise make a new one.
            if (cachedControllers.indexOfKey(subId) >= 0) {
                mMobileSignalControllers.put(subId, cachedControllers.get(subId));
                cachedControllers.remove(subId);
            } else {
                MobileSignalController controller = new MobileSignalController(mContext, mConfig,
                        mHasMobileDataFeature, mPhone.createForSubscriptionId(subId),
                        mCallbackHandler, this, subscriptions.get(i),
                        mSubDefaults, mReceiverHandler.getLooper());
                controller.setUserSetupComplete(mUserSetup);
                mMobileSignalControllers.put(subId, controller);
                if (subscriptions.get(i).getSimSlotIndex() == 0) {
                    mDefaultSignalController = controller;
                }
                if (mListening) {
                    controller.registerListener();
                }
            }
        }
        if (mListening) {
            for (int i = 0; i < cachedControllers.size(); i++) {
                int key = cachedControllers.keyAt(i);
                if (cachedControllers.get(key) == mDefaultSignalController) {
                    mDefaultSignalController = null;
                }
                cachedControllers.get(key).unregisterListener();
            }
        }
        mCallbackHandler.setSubs(subscriptions);
        notifyAllListeners();

        // There may be new MobileSignalControllers around, make sure they get the current
        // inet condition and airplane mode.
        pushConnectivityToSignals();
        updateAirplaneMode(true /* force */);
    }

    private void setUserSetupComplete(final boolean userSetup) {
        mReceiverHandler.post(new Runnable() {
			@Override
			public void run() {
				handleSetUserSetupComplete(userSetup);
			}
		});
    }

    private void handleSetUserSetupComplete(boolean userSetup) {
        mUserSetup = userSetup;
        for (int i = 0; i < mMobileSignalControllers.size(); i++) {
            MobileSignalController controller = mMobileSignalControllers.valueAt(i);
            controller.setUserSetupComplete(mUserSetup);
        }
    }

    
    boolean isUserSetup() {
        return mUserSetup;
    }

    
    boolean hasCorrectMobileControllers(List<SubscriptionInfo> allSubscriptions) {
        if (allSubscriptions.size() != mMobileSignalControllers.size()) {
            return false;
        }
        for (SubscriptionInfo info : allSubscriptions) {
            if (mMobileSignalControllers.indexOfKey(info.getSubscriptionId()) < 0) {
                return false;
            }
        }
        return true;
    }

    
    void setNoNetworksAvailable(boolean noNetworksAvailable) {
        mNoNetworksAvailable = noNetworksAvailable;
    }

    private void updateAirplaneMode(boolean force) {
        boolean airplaneMode = (Settings.Global.getInt(mContext.getContentResolver(),
                Settings.Global.AIRPLANE_MODE_ON, 0) == 1);
        if (airplaneMode != mAirplaneMode || force) {
            mAirplaneMode = airplaneMode;
            for (int i = 0; i < mMobileSignalControllers.size(); i++) {
                MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
                mobileSignalController.setAirplaneMode(mAirplaneMode);
            }
            notifyListeners();
        }
    }

    private void refreshLocale() {
        Locale current = mContext.getResources().getConfiguration().locale;
        if (!current.equals(mLocale)) {
            mLocale = current;
            mWifiSignalController.refreshLocale();
            notifyAllListeners();
        }
    }

    /**
     * Forces update of all callbacks on both SignalClusters and
     * NetworkSignalChangedCallbacks.
     */
    private void notifyAllListeners() {
        notifyListeners();
        for (int i = 0; i < mMobileSignalControllers.size(); i++) {
            MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
            mobileSignalController.notifyListeners();
        }
        mWifiSignalController.notifyListeners();
    }

    /**
     * Notifies listeners of changes in state of to the NetworkController, but
     * does not notify for any info on SignalControllers, for that call
     * notifyAllListeners.
     */
    private void notifyListeners() {
        mCallbackHandler.setIsAirplaneMode(
                new IconState(
                        mAirplaneMode,
                        TelephonyIcons.FLIGHT_MODE_ICON,"Aeroplane mode"));
        mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
    }

    /**
     * Update the Inet conditions and what network we are connected to.
     */
    private void updateConnectivity() {
        mConnectedTransports.clear();
        mValidatedTransports.clear();
        if (mLastDefaultNetworkCapabilities != null) {
            for (int transportType : mLastDefaultNetworkCapabilities.getTransportTypes()) {
                if (transportType != NetworkCapabilities.TRANSPORT_CELLULAR
                        && transportType != NetworkCapabilities.TRANSPORT_WIFI
                        && transportType != NetworkCapabilities.TRANSPORT_ETHERNET) {
                    continue;
                }
                if (transportType == NetworkCapabilities.TRANSPORT_CELLULAR
                        && Utils.tryGetWifiInfoForVcn(mLastDefaultNetworkCapabilities) != null) {
                    mConnectedTransports.set(NetworkCapabilities.TRANSPORT_WIFI);
                    if (mLastDefaultNetworkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
                        mValidatedTransports.set(NetworkCapabilities.TRANSPORT_WIFI);
                    }
                } else {
                    mConnectedTransports.set(transportType);
                    if (mLastDefaultNetworkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
                        mValidatedTransports.set(transportType);
                    }
                }
            }
        }

        if (mForceCellularValidated) mValidatedTransports.set(TRANSPORT_CELLULAR);

        if (CHATTY) {
            Log.d(TAG, "updateConnectivity: mConnectedTransports=" + mConnectedTransports);
            Log.d(TAG, "updateConnectivity: mValidatedTransports=" + mValidatedTransports);
        }

        mInetCondition = mValidatedTransports.get(NetworkCapabilities.TRANSPORT_CELLULAR)
                || mValidatedTransports.get(NetworkCapabilities.TRANSPORT_WIFI)
                || mValidatedTransports.get(NetworkCapabilities.TRANSPORT_ETHERNET);

        pushConnectivityToSignals();
        if (mProviderModelBehavior) {
            mNoDefaultNetwork = !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_CELLULAR)
                    && !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_WIFI)
                    && !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_ETHERNET);
            mCallbackHandler.setConnectivityStatus(mNoDefaultNetwork, !mInetCondition,
                    mNoNetworksAvailable);
            for (int i = 0; i < mMobileSignalControllers.size(); i++) {
                MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
                mobileSignalController.updateNoCallingState();
            }
            notifyAllListeners();
        } else {
            mNoDefaultNetwork = !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_CELLULAR)
                    && !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_WIFI)
                    && !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_ETHERNET);
            mCallbackHandler.setConnectivityStatus(mNoDefaultNetwork, !mInetCondition,
                    mNoNetworksAvailable);
        }
    }

    /**
     * Pushes the current connectivity state to all SignalControllers.
     */
    private void pushConnectivityToSignals() {
        // We want to update all the icons, all at once, for any condition change
        for (int i = 0; i < mMobileSignalControllers.size(); i++) {
            MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
            mobileSignalController.updateConnectivity(mConnectedTransports, mValidatedTransports);
        }
        mWifiSignalController.updateConnectivity(mConnectedTransports, mValidatedTransports);
    }

    /** */
    public void dump(PrintWriter pw, String[] args) {
        pw.println("NetworkController state:");
        pw.println("  mUserSetup=" + mUserSetup);

        pw.println("  - telephony ------");
        pw.print("  hasVoiceCallingFeature()=");
        pw.println(hasVoiceCallingFeature());
        pw.println("  mListening=" + mListening);

        pw.println("  - connectivity ------");
        pw.print("  mConnectedTransports=");
        pw.println(mConnectedTransports);
        pw.print("  mValidatedTransports=");
        pw.println(mValidatedTransports);
        pw.print("  mInetCondition=");
        pw.println(mInetCondition);
        pw.print("  mAirplaneMode=");
        pw.println(mAirplaneMode);
        pw.print("  mLocale=");
        pw.println(mLocale);
        pw.print("  mLastServiceState=");
        pw.println(mLastServiceState);
        pw.print("  mIsEmergency=");
        pw.println(mIsEmergency);
        pw.print("  mEmergencySource=");
        pw.println(emergencyToString(mEmergencySource));

        pw.println("  - DefaultNetworkCallback -----");
        int size = 0;
        for (int i = 0; i < HISTORY_SIZE; i++) {
            if (mHistory[i] != null) {
                size++;
            }
        }
        for (int i = mHistoryIndex + HISTORY_SIZE - 1;
                i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
            pw.println("  Previous NetworkCallback(" + (mHistoryIndex + HISTORY_SIZE - i) + "): "
                    + mHistory[i & (HISTORY_SIZE - 1)]);
        }

        pw.println("  - config ------");
        for (int i = 0; i < mMobileSignalControllers.size(); i++) {
            MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
            mobileSignalController.dump(pw);
        }
        mWifiSignalController.dump(pw);
        mCallbackHandler.dump(pw);
    }

    private static String emergencyToString(int emergencySource) {
        if (emergencySource > EMERGENCY_NO_SUB) {
            return "ASSUMED_VOICE_CONTROLLER(" + (emergencySource - EMERGENCY_VOICE_CONTROLLER)
                    + ")";
        } else if (emergencySource > EMERGENCY_NO_SUB) {
            return "NO_SUB(" + (emergencySource - EMERGENCY_NO_SUB) + ")";
        } else if (emergencySource > EMERGENCY_VOICE_CONTROLLER) {
            return "VOICE_CONTROLLER(" + (emergencySource - EMERGENCY_VOICE_CONTROLLER) + ")";
        } else if (emergencySource > EMERGENCY_FIRST_CONTROLLER) {
            return "FIRST_CONTROLLER(" + (emergencySource - EMERGENCY_FIRST_CONTROLLER) + ")";
        } else if (emergencySource == EMERGENCY_NO_CONTROLLERS) {
            return "NO_CONTROLLERS";
        }
        return "UNKNOWN_SOURCE";
    }

    private boolean mDemoInetCondition;
    private WifiState mDemoWifiState;


    private void recordLastNetworkCallback(String callback) {
        mHistory[mHistoryIndex] = callback;
        mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
    }

    private SubscriptionInfo addSignalController(int id, int simSlotIndex) {
        SubscriptionInfo info = new SubscriptionInfo(id, "", simSlotIndex, "", "", 0, 0, "", 0,
                null, null, null, "", false, null, null);
        MobileSignalController controller = new MobileSignalController(mContext,
                mConfig, mHasMobileDataFeature,
                mPhone.createForSubscriptionId(info.getSubscriptionId()), mCallbackHandler, this,
                info, mSubDefaults, mReceiverHandler.getLooper());
        mMobileSignalControllers.put(id, controller);
        controller.getState().userSetup = true;
        return info;
    }

    /** */
    public boolean isRadioOn() {
        return !mAirplaneMode;
    }

    private class SubListener extends OnSubscriptionsChangedListener {
        SubListener(Looper looper) {
            super(looper);
        }

        @Override
        public void onSubscriptionsChanged() {
            updateMobileControllers();
        }
    }

    /**
     * Used to register listeners from the BG Looper, this way the PhoneStateListeners that
     * get created will also run on the BG Looper.
     */
    private final Runnable mRegisterListeners = new Runnable() {
		@Override
		public void run() {
			registerListeners();
		}
	};

	@Override
	public void addEmergencyListener(EmergencyListener listener) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void removeEmergencyListener(EmergencyListener listener) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public boolean hasEmergencyCryptKeeperText() {
		// TODO Auto-generated method stub
		return false;
	}
}
